package com.davewhoyt.android.pong;

import java.util.LinkedList;
import java.util.Random;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.view.Display;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

public class SimplePongActivity extends Activity {
	private PowerManager mPowerManager;
	private WakeLock mWakeLock;
	private Display mDisplay;
	private WindowManager mWindowManager;
	private SensorManager mSensorManager;

	private CourtView mCourtView;
	private int mScore;
	private int mMisses;
	private int mLevel;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

		// Get an instance of the PowerManager
		mPowerManager = (PowerManager) getSystemService(POWER_SERVICE);


		// Create a bright wake lock
		mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass()
				.getName());

		// Get an instance of the WindowManager
		mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
		mDisplay = mWindowManager.getDefaultDisplay();


		mCourtView = new CourtView(this);
		setContentView(mCourtView);

	}


	@Override
	protected void onResume() {
		super.onResume();
		/*
		 * when the activity is resumed, we acquire a wake-lock so that the
		 * screen stays on, since the user will likely not be fiddling with the
		 * screen or buttons.
		 */
		mWakeLock.acquire();

		// Start the simulation
		mCourtView.startSimulation();
	}

	@Override
	protected void onPause() {
		super.onPause();
		/*
		 * When the activity is paused, we make sure to stop the simulation,
		 * release our sensor resources and wake locks
		 */

		// Stop the simulation
		mCourtView.stopSimulation();

		// and release our wake-lock
		mWakeLock.release();
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
	}


	@Override
	public boolean onOptionsItemSelected(MenuItem item) {

		// Handle item selection
		switch (item.getItemId()) {
		case R.id.new_game:
			mCourtView.resetGame();
			return true;
		case R.id.quit:
			Toast toast=Toast.makeText(this, R.string.quit_message, 2000);
			toast.setGravity(Gravity.TOP, -30, 50);
			toast.show();
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.game_menu, menu);
		return super.onCreateOptionsMenu(menu);
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		mCourtView.pause();
		return super.onPrepareOptionsMenu(menu);
	}
	
	class CourtView extends View implements SensorEventListener {
		private Sensor mAccelerometer;
		
		// not used
//		private float mSensorX;
		private float mSensorY;

		private int mBallX;
		private int mBallY;
		private int mBallVX;
		private int mBallVY;
		private int mBallSize;

		private int mPaddle1Top;
		private int mPaddle1V;
		private int mPaddle2Top;
		private int mPaddle2V;

		// in non-android we might declare these locally as temp objects
		// but that costs a wee bit more cpu for garbage collection
		private Paint mCourtPaint;
		private Paint mPaddleBallTextPaint;
		private Paint mTrailPaint;
		
		private static final int PADDLE_H = 150;
		private static final int PADDLE_W = 15;
		private static final int PADDLE_V = 8;

		private static final String mGameOverString = "SORRY, PLEASE TRY AGAIN!";
		private boolean mGameOver = false;
		private boolean mPaused = false;

		// keep track of the last 10 wall or paddle hits,
		// and if every other one is in the same place, then add some entropy
		// TODO:  is it faster to use raw arrays than to use the java implementation?
		private LinkedList<Integer> mLastTenIntersectX;
		private LinkedList<Integer> mLastTenIntersectY;
		private static final int MAX_SIZE_INTERSECT_STACK = 10;

		// for drawing a "trail" on the screen.
		// TODO:  see above stack vs array
		private LinkedList<Integer> mLastTenBallX;
		private LinkedList<Integer> mLastTenBallY;
		private static final int MAX_SIZE_BALL_HIST_STACK = 25;

		public CourtView(Context context) {
			super(context);
			mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

			mCourtPaint = new Paint();
			mPaddleBallTextPaint = new Paint();
			mTrailPaint = new Paint();
			
			resetGame();
		}

		public void pause() {
			this.mPaused = true;
		}
		public void unPause() {
			this.mPaused = false;
		}

		public void startSimulation() {
			mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
		}

		public void stopSimulation() {
			mSensorManager.unregisterListener(this);
		}

		@Override
		public void onAccuracyChanged(Sensor sensor, int accuracy) {
			// do nothing, we don't care about this, since we're always using the whole screen.
			// this is where we might adjust the ball and paddle size, if we cared about
			// proportion on bigger or smaller screens, but... we don't, for now.
		}

		@Override
		public void onSensorChanged(SensorEvent event) {
			if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) 
				return;


			switch (mDisplay.getOrientation()) {
			case Surface.ROTATION_0:
//				mSensorX = event.values[0];
				mSensorY = event.values[1];
				break;
			case Surface.ROTATION_90:
//				mSensorX = -event.values[1];
				mSensorY = event.values[0];
				break;
			case Surface.ROTATION_180:
//				mSensorX = -event.values[0];
				mSensorY = -event.values[1];
				break;
			case Surface.ROTATION_270:
//				mSensorX = event.values[1];
				mSensorY = -event.values[0];
				break;
			}

			mPaddle1V = (int) (PADDLE_V * mSensorY);
			mPaddle2V = (int) (PADDLE_V * mSensorY);
		}


		@Override
		public boolean onTouchEvent(MotionEvent event) {
			if (mPaused) {
				mPaused = false;
			} else {
				mPaused = true;
			}
			return super.onTouchEvent(event);
		}


		@Override
		protected void onDraw(Canvas canvas) {

			final int height = canvas.getHeight();
			final int width = canvas.getWidth();

			/*
			 * draw the background
			 */
			Rect r = new Rect(0,0, width, height);
			mCourtPaint.setColor(Color.BLACK);
			canvas.drawRect(r, mCourtPaint);


			/*
			 * draw the score
			 */
			mPaddleBallTextPaint.setTextSize(60);
			mPaddleBallTextPaint.setColor(Color.GREEN);

			canvas.drawText("HIT: " + mScore, 140, 200, mPaddleBallTextPaint);
			canvas.drawText("MISS: " + mMisses, 140, 140, mPaddleBallTextPaint);

			String level = "Level: " + mLevel;
			canvas.drawText(level, 140, 260, mPaddleBallTextPaint);


			if (mGameOver) {
				canvas.drawText(mGameOverString, 140, 320, mPaddleBallTextPaint);
			} else {

				if (!mPaused) {
					/*
					 * compute the new position of our ball
					 */
					updateBall(width, height);


					/*
					 * Draw the "trail" for the ball.
					 */
					LinkedList<Integer> listX = mLastTenBallX;
					LinkedList<Integer> listY = mLastTenBallY;
					int size = listX.size();
					int incr = 40 / size;

					for (int count = 0; count < size; count++) {
						int x = listX.get(count);
						int y = listY.get(count);
						mTrailPaint.setColor(Color.GREEN);

						int alpha = count * incr; // increase transparency each count
						int alphaPlus = (alpha / size); // but slightly more each count
						mTrailPaint.setAlpha(alpha + alphaPlus);

						canvas.drawCircle(x, y, mBallSize, mTrailPaint);
					}

					/*
					 * compute position of left and right paddles
					 */
					updatePaddles(width, height);
				} else {
					canvas.drawText("TAP SCREEN TO RESUME", 140, 320, mPaddleBallTextPaint);
				}
				
				canvas.drawRect(0, mPaddle1Top, PADDLE_W, mPaddle1Top + PADDLE_H, mPaddleBallTextPaint);
				canvas.drawRect(width - PADDLE_W, mPaddle2Top, width, mPaddle2Top + PADDLE_H, mPaddleBallTextPaint);
				canvas.drawCircle(mBallX, mBallY, mBallSize, mPaddleBallTextPaint);

			}
			
			
			// and make sure to redraw asap
			invalidate();
		}

		private void updatePaddles(int width, int height) {

			final int nextP1 = mPaddle1Top + mPaddle1V;
			final int nextP2 = mPaddle1Top + mPaddle1V;

			if (nextP1 > height - PADDLE_H || nextP1 < 0 ) {
				mPaddle1V = 0;
			} else { 
				mPaddle1Top = nextP1;
			}

			if (nextP2 > height - PADDLE_H || nextP2 < 0 ) {
				mPaddle2V = 0;
			} else { 
				mPaddle2Top = nextP2;
			}
		}

		private void updateBall(final int width, final int height) {

			int nextX = mBallX + mBallVX;
			int nextY = mBallY + mBallVY;
			final double ballRadius = mBallSize * 0.5;
			final double boundsX =  width - ballRadius;
			final double boundsY = height- ballRadius;
			final boolean paddleHit = isPaddle(nextX, nextY, width, height);
			boolean wallHitX = false;
			boolean wallHitY = false;

			// first see if it's a paddle
			if (paddleHit) {
				mBallVX = -mBallVX;

				mScore++;
				// level up!
				if (mScore > 0 && mScore %10 == 0) {
					mLevel++;
					// speed up ball
					if (mBallVX > 0 ) mBallVX +=2; else mBallVX -=2;
					if (mBallVY > 0 ) mBallVY +=2; else mBallVY -=2;

					// speed up paddle
					if (mPaddle1V > 0) mPaddle1V += 1; else mPaddle1V -=1;
					if (mPaddle2V > 0) mPaddle2V -= 1; else mPaddle2V -=1;
				}

			} else {
				// side wall?
				if (nextX >= boundsX || nextX <= mBallSize * 0.5) {
					if (!paddleHit) {
						// if paddle hit, then direction is changed where that is detected.
						mBallVX = -mBallVX;

						// game status
						mMisses++;
						if (mMisses == 10) mGameOver = true;
					}

					nextX = mBallX + mBallVX;
					wallHitX = true;
				}
				mBallX = nextX;


				// top or bottom wall
				if (nextY >= boundsY || nextY <= mBallSize * 0.5) {
					mBallVY = -mBallVY;
					nextY = mBallY + mBallVY;
				}

				mBallY = nextY;
				wallHitY = true;
			}

			// when all computations are done, update the history stacks
			mLastTenBallX.add(mBallX);
			mLastTenBallY.add(mBallY);
			if (mLastTenBallX.size() > MAX_SIZE_BALL_HIST_STACK) {
				// x and y are always the same size
				mLastTenBallX.poll();
				mLastTenBallY.poll();
			}

			if (wallHitY || wallHitX || paddleHit) {
				mLastTenIntersectX.add(mBallX);
				mLastTenIntersectY.add(mBallY);
				if (mLastTenIntersectX.size() > MAX_SIZE_INTERSECT_STACK) {
					// x and y are always the same size
					mLastTenIntersectX.poll();
					mLastTenIntersectY.poll();
				}

				// compensate if stuck
				if (ballInLoop()) {
					Random r = new Random();
					int entropy = r.nextInt(5);

					if (wallHitX || paddleHit)
						mBallY += entropy;
					else
						mBallX += entropy;
				}
			}
		}



		private boolean ballInLoop() {
			boolean xSame = false;
			boolean ySame = false;

			int size = mLastTenIntersectX.size();
			for (int i = 0; i < size - 1; i++) {
				int x = mLastTenIntersectX.get(i);
				int x2 = mLastTenIntersectX.get(i+1);
				if (x == x2) {
					xSame = true;
					break;
				}
			}


			if (xSame) {
				for (int i = 0; i < size - 1; i++) {
					int y = mLastTenIntersectY.get(i);
					int y2 = mLastTenIntersectY.get(i+1);
					if (y == y2) {
						ySame = true;
						break;
					}
				}
			}

			return xSame && ySame;
		}



		private boolean isPaddle(final int nextX, final int nextY, final int width, final int height) {
			boolean ret = false;

			final double ballRadius = mBallSize * 0.5;
			final double leftBoundsX =  ballRadius;
			final double rightBoundsX = width - ballRadius;

			final double leftPaddleBoundsX = leftBoundsX + PADDLE_W;
			final double rightPaddleBoundsX = rightBoundsX - PADDLE_W;

			final int paddle1Bottom = mPaddle1Top + PADDLE_H;


			// if nextX is less than the width of the paddle (left or right)
			// and nextY is between the top and bottom of the paddle
			if ((nextX <= leftPaddleBoundsX || nextX >= rightPaddleBoundsX ) &&
					(nextY >= mPaddle1Top && nextY <= paddle1Bottom)) {
				ret = true;
			}
			return ret;
		}


		void resetGame() {
			mGameOver = false;
			mScore = 0;
			mMisses = 0;

			mScore = 0;
			mLevel = 0;

			mBallVX = 8;
			mBallVY = 4;
			mBallX = 50;
			mBallY = 100;
			mBallSize = 20;

			mPaddle1Top = 0;
			mPaddle1V = 0;
			mPaddle2Top = 0;
			mPaddle2V = 0;


			mLastTenIntersectX = new LinkedList<Integer>();
			mLastTenIntersectY = new LinkedList<Integer>();

			mLastTenBallX = new LinkedList<Integer>();
			mLastTenBallY = new LinkedList<Integer>();
		}

	}
}